using System;
using System.CodeDom.Compiler;
using System.ComponentModel;
using System.Reflection;
using System.Xml;
using Microsoft.VisualBasic;
using System.Web;

namespace ArcEngine.Resources
{

	/// <summary>
	/// Script resource
	/// </summary>
	public class Script : ResourceBase
	{

		/// <summary>
		/// constructor
		/// </summary>
		/// <param name="name"></param>
		public Script(string name) : base(name)
		{
			// Configure parameters
			Params = new CompilerParameters();
			Params.GenerateExecutable = false;
			Params.GenerateInMemory = true;
			Params.IncludeDebugInformation = false;
			Params.WarningLevel = 3;


			AddReference("ArcEngine.dll");
			AddReference("System.Drawing.dll");
			AddReference("System.Xml.dll");
			AddReference("System.Windows.Forms.dll");
		}

		
		/// <summary>
		/// This is the function that examines a loaded assembly for a type implementing 
		/// a given interface and returns an instance of that class
		/// </summary>
		/// <param name="name">Name of the given Interface</param>
		/// <returns></returns>
		public object FindInterface(string name)
		{
			if (Dll == null)
				return null;

			// Loop through types looking for one that implements the given interface
			foreach (Type t in Dll.GetTypes())
			{
				if (t.GetInterface(name, true) != null)
					return Dll.CreateInstance(t.FullName);
			}

			return null;
		}


		/// <summary>
		/// Compiles a given string script
		/// </summary>
		/// <param name="showlog">Sends the compilation log directly to the log</param>
		/// <returns>True if compilation ok</returns>
		public bool Compile(bool showlog)
		{
			// Already compiled ?
			if (isCompiled)
				return true;

			isCompiled = false;
			isModified = false;
			hasErrors = false;
			errors = null;
			Dll = null;

			// Generate the compiler provider
			switch (language)
			{
				case ScriptLanguage.CSharp:
				{
					ScriptProvider = new Microsoft.CSharp.CSharpCodeProvider();
					if (ScriptProvider == null)
					{
						Log.Send(new LogEventArgs(LogLevel.Error, "Failed to initialize a new instance of the CSharpCodeProvider class", null));
						return false;
					}
				}
				break;

				case ScriptLanguage.VBNet:
				{
					ScriptProvider = new Microsoft.VisualBasic.VBCodeProvider();
					if (ScriptProvider == null)
					{
						Log.Send(new LogEventArgs(LogLevel.Error, "Failed to initialize a new instance of the VBCodeProvider class", null));
						return false;
					}
				}
				break;

				default:
				{
					Log.Send(new LogEventArgs(LogLevel.Error, "Unknown scripting language !!!", null));
					return false;
				}

			}




			// Compile
			CompilerResults results = ScriptProvider.CompileAssemblyFromSource(Params, sourceCode);

			// Compilation ok ?
			if (results.Errors.Count == 0)
			{
				Dll = results.CompiledAssembly;
				isCompiled = true;
			}
			else
			{
				errors = results.Errors;
				hasErrors = true;

				if (showlog)
				{
					Log.Send(new LogEventArgs(LogLevel.Info,
						"Compile complete -- " + Errors.Count + " error(s) in script \"" + Name + "\" :",
						null));

					string txt;
					LogLevel lvl;
					foreach (CompilerError error in Errors)
					{
						txt = "line " + error.Line + " : " + error.ErrorText;

						if (error.IsWarning)
							lvl = LogLevel.Warning;
						else
							lvl = LogLevel.Error;

						Log.Send(new LogEventArgs(lvl, txt, null));
					}
				}
			}


			return isCompiled;
		}



		/// <summary>
		/// Adds reference to the assembly
		/// </summary>
		/// <param name="name">Name of the reference</param>
		public void AddReference(string name)
		{
			Params.ReferencedAssemblies.Add(name);
		}




		#region IO routines

		///
		///<summary>
		/// Save the collection to a xml node
		/// </summary>
		public override bool Save(XmlWriter xml)
		{
			if (xml == null)
				return false;

			xml.WriteStartElement("script");
			xml.WriteAttributeString("name", Name);


			base.SaveComment(xml);

			xml.WriteStartElement("source");
			xml.WriteAttributeString("language", Language.ToString());
			xml.WriteRaw(HttpUtility.HtmlEncode(SourceCode));
			xml.WriteEndElement();

			xml.WriteStartElement("debug");
			xml.WriteAttributeString("value", DebugMode.ToString());
			xml.WriteEndElement();

			xml.WriteEndElement();

			return true;
		}


		/// <summary>
		/// 
		/// </summary>
		/// <param name="xml"></param>
		/// <returns></returns>
		public override bool Load(XmlNode xml)
		{
			foreach (XmlNode node in xml)
			{
				if (node.NodeType == XmlNodeType.Comment)
				{
					base.LoadComment(node);
					continue;
				}

				switch (node.Name.ToLower())
				{
					case "source":
					{

						sourceCode = "";
						try
						{
							language = (ScriptLanguage)Enum.Parse(typeof(ScriptLanguage), node.Attributes["language"].Value, true);
							sourceCode = HttpUtility.HtmlDecode(xml.InnerText);
						}
						catch (ArgumentException e)
						{
							Log.Send(new LogEventArgs(LogLevel.Error, "Couldn't detect language version for script \"" + Name  + "\"", e.StackTrace));
						}

					}
					break;


					case "debug":
					{
						DebugMode = Boolean.Parse(node.Attributes["value"].Value);
					}
					break;

					default:
					{
						Log.Send(new LogEventArgs(LogLevel.Warning, "Script : Unknown node element found (" + node.Name + ")", null));
					}
					break;
				}
			}


			return true;
		}

		#endregion


		#region Properties


		/// <summary>
		/// Compiler parameters
		/// </summary>
		CompilerParameters Params;


		/// <summary>
		/// Source code of the script
		/// </summary>
		[Browsable(false)]
		public string SourceCode
		{
			get
			{
				return sourceCode;
			}

			set
			{
				sourceCode = value;
				isModified = true;
				isCompiled = false;
			}
		}
		string sourceCode;


		/// <summary>
		/// Enables / disables the debug mode
		/// </summary>
		[CategoryAttribute("Debug")]
		[Description("Enables / disables the debug mode")]
		public bool DebugMode
		{
			get
			{
				return Params.IncludeDebugInformation;
			}
			set
			{
				Params.IncludeDebugInformation = value;
			}
		}

		/// <summary>
		/// Is source code modified ?
		/// </summary>
		[Browsable(false)]
		public bool IsModified
		{
			get
			{
				return isModified;
			}
		}
		bool isModified;



		/// <summary>
		/// Returns the state of the script compilation
		/// </summary>
		[Browsable(false)]
		public bool IsCompiled
		{
			get
			{
				return isCompiled;
			}
		}
		bool isCompiled;



		/// <summary>
		/// Gets the compilation log
		/// </summary>
		[Browsable(false)]
		public CompilerErrorCollection Errors
		{
			get
			{
				return errors;
			}
		}
		CompilerErrorCollection errors;


		/// <summary>
		/// Does the script contains error
		/// </summary>
		[Browsable(false)]
		public bool HasErrors
		{
			get
			{
				return hasErrors;
			}
		}
		bool hasErrors;



		/// <summary>
		/// Compiled script
		/// </summary>
		Assembly Dll;

		/// <summary>
		/// Script langage
		/// </summary>
		[CategoryAttribute("Language")]
		[Description("Script's language")]
		public ScriptLanguage Language
		{
			get
			{
				return language;
			}
			set
			{
				language = value;
			}
		}
		ScriptLanguage language;



		/// <summary>
		/// http://www.divil.co.uk/net/articles/plugins/scripting.asp
		/// </summary>
		/*static*/ CodeDomProvider ScriptProvider;


		#endregion

	}

	/// <summary>
	/// Supported languages
	/// </summary>
	public enum ScriptLanguage
	{
		/// <summary>
		/// CSharp language
		/// </summary>
		CSharp,

		/// <summary>
		/// VB.Net language
		/// </summary>
		VBNet
	}




}
